package master

import (
	"GoCrontab/crontab/common"
	"context"
	"encoding/json"
	"fmt"
	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/mvcc/mvccpb"
	"time"
)

var (
	GlobalJobManager *JobManager
)

//master的任务管理器
type JobManager struct {
	client *clientv3.Client
	kv     clientv3.KV
	lease  clientv3.Lease
}

//etcd --listen-client-urls 'http://0.0.0.0:2379' --advertise-client-urls 'http://0.0.0.0:2379'
func InitJobManager() (err error) {
	var (
		config clientv3.Config
		client *clientv3.Client
		kv     clientv3.KV
		lease  clientv3.Lease
	)
	config = clientv3.Config{
		Endpoints: GlobalConfig.EtcdEndPoints,
		Username:  GlobalConfig.EtcdUserName,
		Password:  GlobalConfig.EtcdPassword,
		//Millisecond 毫秒,1毫秒等于千分之一秒
		// MicroSecond 微秒,1微秒等于百万分之一秒
		DialTimeout: time.Duration(GlobalConfig.EtcdDialTimeout) * time.Millisecond,
	}

	if client, err = clientv3.New(config); err != nil {
		return
	}
	//得到KV和Lease的api子集
	kv = clientv3.NewKV(client)
	lease = clientv3.NewLease(client)

	fmt.Println("ectd success")

	GlobalJobManager = &JobManager{
		client: client,
		kv:     kv,
		lease:  lease,
	}

	return

}

func (JobManager *JobManager) SaveJob(job *common.Job) (oldJob *common.Job, err error) {
	var (
		jobKey      string
		jobValue    []byte
		putResponse *clientv3.PutResponse

		oldJobObj common.Job
	)
	//把任务保存到这个目录 /cron/jobs/name > json,接收值为interface，返回byte数组,可以用string转成更好看
	if jobValue, err = json.Marshal(job); err != nil {
		return
	}
	// 此处以jobname为key
	jobKey = common.JOB_SAVE_DIR + job.Name
	if putResponse, err = JobManager.kv.Put(context.TODO(), jobKey, string(jobValue), clientv3.WithPrevKV()); err != nil {
		//fmt.Println("kv error:", err.Error())
		return
	}
	//如果是更新，会返回旧值，但是还需要判断一下
	if putResponse.PrevKv != nil {
		if err = json.Unmarshal(putResponse.PrevKv.Value, &oldJobObj); err != nil {
			//如果解析旧值失败，无大碍
			fmt.Println("有旧值：", putResponse.PrevKv.Value)
			err = nil
			return
		}
		oldJob = &oldJobObj
	}

	return
}

//删除任务的原理是 ：向 /cron/killer/任务名写入，worker会监听watch，然后kill
func (JobManager *JobManager) KillJob(jobName string) (killedJobValue string, err error) {
	var (
		killJobKey string
		leaseResp  *clientv3.LeaseGrantResponse
		leaseId    clientv3.LeaseID
		putResp    *clientv3.PutResponse
	)
	killJobKey = common.JOB_Kill_DIR + jobName
	//让worker 监听到这个目录的put操作，最好设置这个key的过期时间，只需通知即可
	if leaseResp, err = GlobalJobManager.lease.Grant(context.TODO(), 1); err != nil {
		return
	}
	leaseId = leaseResp.ID
	//1 秒后租约过期，但各个worker都会watch到，而且不需要续租,不关心putResponse的结果
	if putResp, err = GlobalJobManager.kv.Put(context.TODO(), killJobKey, time.Now().Format("2006-01-02 15:04:05"), clientv3.WithLease(leaseId), clientv3.WithPrevKV()); err != nil {
		return
	}
	//如果是kill，前提应该是存在，但是还需要判断一下.但是这里是不会出现的，因为key都不一样（前缀一个kill，一个job）
	if putResp.PrevKv != nil {
		killedJobValue = string(putResp.PrevKv.Value)
		return killedJobValue, nil
	}
	fmt.Println("put kill 成功", killJobKey)
	return
}
func (JobManager *JobManager) DelJob(jobName string) (oldJob *common.Job, delCount int64, err error) {

	var (
		delResponse *clientv3.DeleteResponse
		jobkey      string
		oldJobObj   common.Job
	)

	jobkey = common.JOB_SAVE_DIR + jobName
	//如果不存在，prevKV为空，
	if delResponse, err = JobManager.kv.Delete(context.TODO(), jobkey, clientv3.WithPrevKV()); err != nil {
		//fmt.Println("kv error:", err.Error())
		return nil, 0, err
	}
	delCount = delResponse.Deleted

	if len(delResponse.PrevKvs) != 0 {
		if err = json.Unmarshal(delResponse.PrevKvs[0].Value, &oldJobObj); err != nil {
			//如果解析旧值失败，无大碍,此处是删除的制定的唯一key，不考虑prefix的情况
			fmt.Println("有旧值：", delResponse.PrevKvs[0].Value)
			err = nil
			return
		}
		oldJob = &oldJobObj
		return oldJob, delCount, err
	}
	return nil, delCount, err
}

//返回的是一个指针数组，为啥得是个指针呢，考虑直接数组可以不？
func (JobManager *JobManager) ListJobs() (jobList []*common.Job, err error) {

	var (
		dirKey      string
		getResponse *clientv3.GetResponse
		kvPair      *mvccpb.KeyValue
		job         *common.Job
	)
	dirKey = common.JOB_SAVE_DIR

	if getResponse, err = JobManager.kv.Get(context.TODO(), dirKey, clientv3.WithPrefix()); err != nil {
		return
	}

	//需要初始化数组，可以长度为0，但是不能为nil.这样调用者也可以直接判断返回值为0还是空指针 len(jobList)=0
	jobList = make([]*common.Job, 0)
	for _, kvPair = range getResponse.Kvs {
		//声明一个指针，反序列化用
		err = nil
		job = &common.Job{}
		if err = json.Unmarshal(kvPair.Value, job); err != nil {
			continue
		}
		//这里涉及到内存的重新拷贝和接收
		jobList = append(jobList, job)
	}
	return

}
